﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;

namespace Tecnicas.CasoEstudio.Repositorio
{
    public class RepositorioVersionado
    {
        private string repositoryPhysicalPath = Path.GetDirectoryName(Assembly.GetAssembly(typeof(RepositorioVersionado)).CodeBase).Replace("file:\\", "") + "\\";
        
        private long nextKey = 0;


        private string GetRepositoryPhysicalName() 
        {
            string repositoryName = System.Configuration.ConfigurationSettings.AppSettings["RepositoryName"];
            return this.repositoryPhysicalPath + repositoryName + ".items.dat";
        }

        
        private RepositorioVersionado() 
        {
            // Incializa el repositorio a partir de su versión persistida
            this.Cargar();
        }

        public long GetNextKey() 
        {
            return ++nextKey;
        }

        private static RepositorioVersionado _instance = null;
        /// <summary>
        /// Obtiene la instancia única del repositorio
        /// </summary>
        public static RepositorioVersionado Instance
        {
            get
            {
                if (RepositorioVersionado._instance == null) RepositorioVersionado._instance = new RepositorioVersionado();
                return RepositorioVersionado._instance;
            }
        }

        private Hashtable items = new Hashtable();        


        /// <summary>
        /// Persiste el repositorio
        /// </summary>
        public void Persistir() 
        {
            items["nextKey"] = this.nextKey;
            Serializer.SerializeObject(this.GetRepositoryPhysicalName(), items);
        }

        public void Inicializar()
        {
            items = new Hashtable();
            nextKey = 0;            
        }
        private void Cargar()
        {
            if (File.Exists(this.GetRepositoryPhysicalName()))
            {
                items = (Hashtable)Serializer.DeSerializeObject(this.GetRepositoryPhysicalName());
                this.nextKey = Convert.ToInt64(items["nextKey"]);
            }
        }

        public object ObtenerPorId(Type tipo, long? id)
        {
            RepositorioItem item = ObtenerItemPorId(tipo,id);
            if (item==null)
                throw new ApplicationException("No existe el objeto con dicho id");

            return this.Clonar(item.Item);
        }

        public ArrayList ObtenerPorTipo(Type tipo)
        {
            ArrayList lista = new ArrayList();

            if (items[tipo] == null)
                return lista;

            foreach (RepositorioItem item in ((Hashtable)items[tipo]).Values) 
            {
                lista.Add(this.Clonar(item.Item));
            }
            return lista;
        }

        private RepositorioItem ObtenerItemPorId(Type tipo, long? id)
        {
            if (!this.Existe(tipo, id))
                return null;

            return (RepositorioItem)((Hashtable)items[tipo])[id];
        }
       

        private bool ExistenDeEsteTipo(Type tipo) 
        {
            return !(items[tipo] == null);
        }

        private bool Existe(Type tipo, long? id)
        {
            if (!this.ExistenDeEsteTipo(tipo))
                return false;

            return !(((Hashtable)items[tipo])[id] == null);
        }

        public void Guardar(object obj, long? id, object version, object nuevaVersion)
        {
            if (!this.VersionEsValida(obj.GetType(), id, version))
                throw new ConcurrenciaException("Numero de Version invalida");

            if (!this.ExistenDeEsteTipo(obj.GetType())) 
                items[obj.GetType()] = new Hashtable();
            
            ((Hashtable)items[obj.GetType()]).Add(id, new RepositorioItem(this.Clonar(obj), id, nuevaVersion));
            return;
        }

        private bool VersionEsValida(Type type, long? id, object version) 
        {
            RepositorioItem item = ObtenerItemPorId(type, id);
            return (item == null || item.Version.ToString() == version.ToString());
        }

        public void Eliminar(Type type, long? id, object version)
        {
            if (!this.VersionEsValida(type, id, version))
                throw new ConcurrenciaException("Numero de Version invalida");

            if (!this.ExistenDeEsteTipo(type))
            {
                throw new ApplicationException("No existe el objeto a eliminar");
            }

            if (!this.Existe(type, id))
            {
                throw new ApplicationException("No existe el objeto a eliminar");
            }

            ((Hashtable)items[type])[id] = null;
            
        }

        private object Clonar(object objectToClone)
        {
            MemoryStream ms = new MemoryStream();
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(ms, objectToClone);
            ms.Position = 0;
            object obj = bf.Deserialize(ms);
            ms.Close();
            return obj;
        }

        public ArrayList ObtenerPorAtributo(Type tipo, string nombrePropiedad, object valor)
        {
            ArrayList lista = new ArrayList();
            
            foreach(RepositorioItem item in ((Hashtable)items[tipo]).Values) 
            {
                object propiedadValor = item.Item.GetType().GetProperty(nombrePropiedad).GetValue(item.Item, null);
                if (propiedadValor.Equals(valor))
                    lista.Add(item.Item);
            } 

            return lista;
        }

    }
}
